home *** CD-ROM | disk | FTP | other *** search
/ Complete Linux / Complete Linux.iso / docs / apps / database / postgres / postgre2.z / postgre2 / doc / implementation / planner / 4.imple < prev    next >
Encoding:
Text File  |  1992-08-27  |  11.8 KB  |  285 lines

  1. .sh 1 "IMPLEMENTATION ISSUES" 4
  2. .pp
  3. This section does not attempt to describe in detail the actual implementation
  4. of the POSTGRES optimizer.
  5. Rather, it focuses on important decisions made in building the optimizer.
  6. .sh 2 "Choice of Language"
  7. .pp
  8. Query optimization requires a great deal of element manipulation.
  9. The optimizer must separate, modify, and regroup elements within a query's
  10. target list and qualification to create new components
  11. for individual nodes in the plan tree.
  12. A programming language like LISP is well-suited for this type of processing
  13. because the language contains functions and data structures specifically
  14. designed for object manipulation tasks.
  15. Consequently, for ease of implementation, we chose to write the POSTGRES 
  16. query optimizer in LISP.
  17. .pp
  18. Franz LISP, Opus 42 [FRAN85] was the selected LISP dialect. 
  19. It was chosen because it was readily available and also because it supports 
  20. a foreign function interface.
  21. A foreign function interface allows LISP code to call routines written 
  22. in other programming languages.
  23. This feature is of utmost importance because 
  24. the optimizer must call C routines 
  25. to access information from database system catalogs,
  26. given that the access method code in POSTGRES is being written in C.
  27. .pp
  28. Franz LISP, Opus 42 is also fairly compatible with CommonLISP [STEE84], an
  29. emerging standard LISP dialect.
  30. Therefore, in the future, if translation from Franz to CommonLISP is necessary,
  31. this will require minimal effort.
  32. .pp
  33. In general, compiled LISP code executes less efficiently than compiled C code.
  34. Therefore, an optimizer written in LISP will execute more slowly than
  35. an optimizer written in C.
  36. This, however, is not a problem.
  37. As discussed in section 3.1, POSTGRES compiles query plans and caches,
  38. for later use,
  39. plans and tuples resulting from query preexecution.
  40. Because of these two features, a single query plan produced by the optimizer
  41. may be used several times.
  42. As a result, the cost of optimization is amortized
  43. over several executions.
  44. This significantly reduces a query's planning cost,
  45. yielding a figure that is minimal relative to
  46. the overall cost of execution.
  47. Therefore, in terms of optimizer efficiency,
  48. the choice of language is not a major concern.
  49. .sh 2 "Representing Query Plans in LISP"
  50. .pp
  51. In general, the cost of query processing constitutes the most
  52. significant portion of a query's execution cost.
  53. Therefore, the query processor must execute as cost efficiently as possible.
  54. To meet this goal, every node in the plan tree is constructed
  55. using one-dimensional arrays.
  56. These are known as \*(lqvectors\*(rq in LISP.
  57. Each element within a vector corresponds to some property of a node.
  58. By indexing appropriate vector entries, all properties can be accessed in
  59. constant time.
  60. .pp
  61. Among the properties within each plan node are the left and
  62. right subtrees of the node, target lists, and qualifications.
  63. The left and right subtrees either point to another plan node or
  64. nothing (\fBnil\fR in LISP).
  65. The target list and qualification entries respectively point to a 
  66. list of individual target list elements and a list of restriction clauses.
  67. Lists are used to represent these structures because
  68. both sets of items are variable length,
  69. and random access to individual entries within these lists is not required.
  70. Each target list item consists of two items, 
  71. also grouped together in a list.
  72. The first item in the list is a \*(lqresdom\*(rq node.
  73. It contains information about its corresponding 
  74. target list entry \*-
  75. its type, destination, and if relevant, sort or hash information.
  76. Each resdom node is implemented using a vector.
  77. The second element, an \fIexpr\fR, is an arbitrary arithmetic expression
  78. consisting of
  79. variables, constants, parameters, functions, and operators.
  80. Each of these subcomponents is also a vector, and these vectors are linked
  81. together in a list if they
  82. represent the arguments to a particular operation or function. 
  83. A restriction clause is a boolean \fIexpr\fR; therefore the preceding
  84. description applies to qualifications as well.
  85. .pp
  86. In addition, every plan node contains an empty slot that the query processor
  87. uses to store runtime-specific query information.
  88. Figure 4.1 shows the internal representation of a query plan that accesses
  89. two attributes and processes a single join clause using nested iteration.
  90. .(z
  91. .hl
  92. .GS
  93. file figure4.1
  94. .GE
  95. .sp
  96. .ce 2
  97. \fBFigure 4.1\fR
  98. Internal representation of a nested iteration join node
  99. .hl
  100. .)z
  101. .pp
  102. Constructs analogous to records in Pascal and \*(lqstructs\*(rq in C
  103. are used to build
  104. the different vector types associated with each node type.
  105. These constructs are called \*(lqdefstructs\*(rq in LISP.
  106. With defstructs, LISP programmers can combine primitive data types to create
  107. structured items.
  108. These new data structures, in turn, can be combined and nested to create even
  109. more complex structures.
  110. After defining a defstruct, LISP automatically provides a 
  111. set of routines to dynamically create objects of these structured types
  112. and to access information within a structure.
  113. As a result, although a vector is the underlying data type used to
  114. implement defstructs, users can access node properties by specifying
  115. field names, as opposed to indexing vector entries.
  116. Figure 4.2 shows a Franz LISP defstruct definition and associated routines
  117. for a nestloop node.
  118. .(z
  119. .hl
  120. .(l
  121. .sz \n(ep
  122. .ft I
  123. ;    Plan information common to all plan nodes
  124. .ft R
  125.  
  126. (\fBdefstruct\fR (plannode 
  127.         (\fB:conc-name\fR get_))
  128.         (state \fBnil\fR)
  129.         targetlist
  130.         (lefttree \fBnil\fR)
  131.         (righttree \fBnil\fR))    
  132.             \fI; initialize lefttree, righttree, and state to nil\fR
  133.  
  134. .ft I
  135. ;    Nestloop node
  136. .ft R
  137.  
  138. (\fBdefstruct\fR (nestloop 
  139.         (\fB:include\fR plannode)    
  140.             \fI; node contains defstruct defined above\fR
  141.         (\fB:conc-name\fR get_)
  142.         (\fB:constructor\fR make_nestloop (targetlist qual lefttree righttree)))
  143.         (nodetype \*(lqNESTLOOP\*(rq)
  144.         qual)
  145. .sp
  146. .ft I
  147. ;
  148. ;    LISP routines provided as a result of the above definitions:
  149. ;
  150. ;    Routines to retrieve property fields:
  151.  
  152. (\fBget_state \fRnode)
  153. (\fBget_targetlist\fR node)
  154. (\fBget_lefttree\fR node)
  155. (\fBget_righttree\fR node)
  156. (\fBget_nodetype\fR node)
  157. (\fBget_qual \fRnode)
  158.  
  159. \fI;    Routine to construct a nestloop node:\fR
  160.  
  161. (\fBmake_nestloop\fR targetlist qual lefttree righttree)
  162. .sz \n(pp
  163. .)l
  164. .sp 2
  165. .ce 2
  166. \fBFigure 4.2\fR
  167. Sample defstruct definition
  168. .hl
  169. .)z
  170. .sh 2 "Internal Data Structures"
  171. .pp
  172. In the process of creating possible query plans, the optimizer generates
  173. a great deal of information.
  174. To keep track of all this information, a more flexible structure of 
  175. LISP is used \*- property lists.
  176. Every LISP object may have associated with it a list of characteristics,
  177. set by the user, called a property list. 
  178. A major advantage of property lists is that one does not have to
  179. preallocate space for property slots, as required for defstructs.
  180. As a result, at any given time,
  181. every object may have an arbitrary number of properties associated with it.
  182. .pp
  183. Property lists are implemented using linked lists.
  184. Thus, access to information within a property list 
  185. requires a linear search.
  186. The inefficiency of linear search is not a problem here because
  187. generally, the optimizer does not store any more than four or five items
  188. within a single property list,
  189. and as indicated in section 4.1, efficiency is not a primary
  190. consideration in this part of the system.
  191. Therefore, because property lists are simpler to work with, they are
  192. used extensively within the optimizer.
  193. .sh 2 "LISP-C Interface"
  194. .pp
  195. The foreign function interface in LISP is fairly easy to work with, provided
  196. a set of stringent rules are followed.
  197. For example, the standard way to pass structured information (e.g.
  198. a character string) from a C function is
  199. to return a pointer to the object.
  200. From here, the user can manipulate information within the
  201. object by referencing the pointer.
  202. This, however, will not work when LISP calls C because
  203. LISP cannot manipulate objects that C has allocated.
  204. It presents problems for the LISP garbage collector.
  205. .pp
  206. To work around this, C can return structured information by
  207. loading the data into variables that LISP has passed as parameters.
  208. Space for these return variables must be allocated by LISP prior to the C call.
  209. This is straightforward provided LISP knows the size of the returning object
  210. and can set aside a sufficient amount of memory.
  211. However, this is not always the case because tuples returned by
  212. C access method routines are variable length.
  213. .pp
  214. Fortunately, the optimizer never requires the contents of an entire tuple;
  215. on all occasions, it only needs a fixed set of attributes from 
  216. within a single tuple.
  217. Therefore, rather than attempt to directly manipulate arbitrary tuples
  218. returned by access method routines, a layer written in C was built between
  219. the optimizer and the access method code.
  220. When the optimizer needs information from system catalogs, it calls
  221. some routine within this layer, which then calls access method routines to
  222. retrieve tuples.
  223. Desired information within these tuples are either returned explicitly
  224. as integers and floats, or they are passed back
  225. within variables allocated by LISP.
  226. .pp
  227. As an example, the optimizer may call a C routine, within the layer, called
  228. \*(lqretrieve_index\*(rq to retrieve information about a secondary index.
  229. In calling the routine, LISP passes a pointer to an integer array
  230. \*(lqindexinfo.\*(rq
  231. \*(lqRetrieve_index\*(rq then calls the access method routine \*(lqgetnext\*(rq
  232. until an appropriate tuple from the index catalog has been located.
  233. The index identifier, the number of pages in the index, and any other
  234. relevant information are extracted from the tuple and
  235. passed back to the optimizer in the array \*(lqindexinfo.\*(rq
  236. Consequently, variable length tuples are handled solely by C,
  237. resulting in a cleaner and simpler LISP-C interface.
  238. .sh 2 "An Evaluation of Using LISP"
  239. .pp
  240. Overall, writing the POSTGRES optimizer required about 6500 lines of LISP
  241. code and another 700 lines of C code.
  242. Having written the optimizer, using LISP was an excellent choice.
  243. There was a close match between our processing needs and the 
  244. constructs and functions LISP provides.
  245. As a result, the programming effort was simplified.
  246. Had we used a language like C, we would have had to explicitly implement
  247. structures and routines equivalent to those LISP provides.
  248. .pp
  249. While writing the optimizer, it was also evident that
  250. other features of LISP were instrumental in simplifying code development.
  251. For instance, LISP allows you to either interpret or compile code written
  252. in the language.
  253. Naturally, compiled code is used in POSTGRES, but in developing
  254. the optimizer, the interpretive option was used.
  255. This significantly reduced development time because debugging was simpler and
  256. compilation time was eliminated.
  257. .pp
  258. LISP also supports dynamic allocation and implicit recollection of free
  259. space.
  260. The latter is implemented using garbage collection.
  261. As a result of these two properties, the optimizer can easily create 
  262. objects of any type when needed, and LISP automatically handles memory
  263. management issues.
  264. .pp
  265. Last of all, LISP is a weakly typed language and
  266. because no single type is associated with variables in weakly
  267. typed languages, union types
  268. were implicit and variable declarations were unnecessary.
  269. This further resulted in simpler data structure definitions because
  270. declaration of field types was also unnecessary, as shown in figure 4.2.
  271. Another advantage of weakly typed languages is the absence of strict type 
  272. checking.
  273. As a result,
  274. there is a certain degree of independence between the
  275. parameters a routine accepts and those that are actually passed.
  276. For example, if a routine accepts an identifier as a parameter but does
  277. not manipulate its actual value, then whether the identifier is an integer or
  278. string is not significant; choosing one or the
  279. other will not affect code within the routine.
  280. In many situations, this characteristic allowed us to make changes without
  281. modifying other relevant pieces of code.
  282. Changes could be made much more quickly as a result.
  283. So to briefly summarize, LISP was a simpler and much more flexible language to
  284. work with.
  285.